package com.yc.framework.security.filter;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.github.benmanes.caffeine.cache.Cache;
import com.yc.common.core.redis.RedisCache;
import com.yc.common.exceptions.ServiceException;
import com.yc.common.core.domain.model.LoginUser;
import com.yc.common.utils.JwtUtil;
import com.yc.common.utils.StringUtil;
import com.yc.common.utils.WebUtil;
import com.yc.framework.web.service.TokenService;
import com.yc.system.service.ISysUserService;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    private static final String ACCESS_TOKEN_KEY = "access_token:";

    // 失败计数前缀
    private static final String FAILED_ATTEMPTS_IP_PREFIX = "failed_token_ip:";
    private static final String FAILED_ATTEMPTS_USER_PREFIX = "failed_token_user:";
    // 锁定前缀
    private static final String LOCKED_IP_PREFIX = "locked_ip:";
    private static final String LOCKED_USER_PREFIX = "locked_user:";
    // 失败阈值
    private static final int MAX_FAILED_ATTEMPTS = 3;
    // 锁定时间（分钟）
    private static final int LOCK_DURATION_MINUTES = 30;

    @Autowired
    private Cache<String, LoginUser> tokenCache;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private ISysUserService sysUserService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // IP封禁检查保持不变
        String ipAddress = WebUtil.getIpAddr();

        if (isIpLocked(ipAddress)) {
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write("{\"code\":403,\"msg\":\"由于多次验证失败，您的IP已被临时锁定，请"
                    + LOCK_DURATION_MINUTES + "分钟后再试\"}");
            return;
        }

        // 获取token
        String token = WebUtil.getToken();
        if (!StringUtil.hasText(token)) {
            filterChain.doFilter(request, response);
            return;
        }


        try {
            // 验证JWT签名和过期时间
            Claims claims = JwtUtil.parseJWT(token);
            JSONObject payload = JSON.parseObject(claims.getSubject());

            // 获取基本信息
            String tokenType = payload.getString("typ");
            if (!"a".equals(tokenType)) {
                throw new ServiceException("无效的token类型", 401);
            }

            String tokenId = payload.getString("jti");
            Long userId = payload.getLong("uid");

            // 检查令牌是否在黑名单中
            if (tokenService.isAccessTokenBlacklisted(tokenId)) {
                throw new ServiceException("令牌已被撤销", 401);
            }

            // 验证令牌是否在Redis白名单中
            String redisKey = ACCESS_TOKEN_KEY + tokenId;
            Map<String, Object> tokenInfo = redisCache.getCacheObject(redisKey);

            // 检查token是否在黑名单中
            if (tokenService.isAccessTokenBlacklisted(tokenId)) {
                throw new ServiceException("令牌已被撤销", 401);
            }

            if (tokenInfo == null) {
                // 如果Redis中不存在，可能是过期了，尝试查询本地缓存
                LoginUser loginUserFromCache = tokenCache.getIfPresent(tokenId);
                if (loginUserFromCache == null || !userId.equals(loginUserFromCache.getUserId())) {
                    throw new ServiceException("令牌已失效，请重新获取", 401);
                }

                // 本地缓存有效，恢复会话并继续处理请求
                log.debug("从本地缓存恢复用户会话: userId={}, tokenId={}", userId, tokenId);
            }

            // 从本地缓存或数据库获取完整的用户信息
            LoginUser loginUser;
            loginUser = tokenCache.getIfPresent(tokenId);

            if (loginUser == null) {
                // 如果本地缓存没有，但Redis存在tokenInfo，从数据库重新加载
                loginUser = sysUserService.createLoginUser(userId);
                if (loginUser != null) {
                    // 更新本地缓存
                    loginUser.getTokenInfo().setTokenId(tokenId);
                    tokenCache.put(tokenId, loginUser);
                } else {
                    throw new ServiceException("用户信息不存在", 401);
                }
            }

            // 验证成功，清除失败计数
            clearFailedAttempts(ipAddress, userId.toString());

            // 设置安全上下文
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(
                            loginUser, null, loginUser.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authentication);

            // 放行请求
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            log.error("Token验证失败: {}", e.getMessage());

            // 尝试从token中获取用户ID
            String userId = null;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                JSONObject jsonObject = JSON.parseObject(claims.getSubject());
                userId = jsonObject.getString("uid");
            } catch (Exception ignored) {
                // 解析失败，无法获取用户ID
            }

            // 增加失败计数
            incrementFailedAttempts(ipAddress, userId);

            // 检查是否应该锁定
            boolean ipLocked = checkAndLockIfNeeded(ipAddress, true);
            boolean userLocked = userId != null && checkAndLockIfNeeded(userId, false);

            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");

            if (ipLocked || userLocked) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.getWriter().write("{\"code\":403,\"msg\":\"由于多次验证失败，您的访问已被临时锁定，请" + LOCK_DURATION_MINUTES + "分钟后再试\"}");
                return;
            }

            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

            String errorMessage = e.getMessage();
            int errorCode = 401;

            if (errorMessage != null && errorMessage.contains("expired")) {
                errorCode = 4011; // 特定的错误码表示token已过期需要刷新
                errorMessage = "访问令牌已过期，请使用刷新令牌";
            }

            response.getWriter().write("{\"code\":" + errorCode + ",\"msg\":\"" + errorMessage + "\"}");
        }
    }

    /**
     * 检查IP是否被锁定
     */
    private boolean isIpLocked(String ipAddress) {
        String key = LOCKED_IP_PREFIX + ipAddress;
        Boolean locked = redisCache.getCacheObject(key);
        return locked != null && locked;
    }

    /**
     * 增加失败计数
     */
    private void incrementFailedAttempts(String ipAddress, String userId) {
        // 增加IP失败计数
        String ipKey = FAILED_ATTEMPTS_IP_PREFIX + ipAddress;
        Integer ipCount = redisCache.getCacheObject(ipKey);
        if (ipCount == null) {
            ipCount = 0;
        }
        // 使用新方法
        redisCache.setCacheObjectWithExpire(ipKey, ipCount + 1, 1, TimeUnit.HOURS);

        // 增加用户失败计数
        if (userId != null) {
            String userKey = FAILED_ATTEMPTS_USER_PREFIX + userId;
            Integer userCount = redisCache.getCacheObject(userKey);
            if (userCount == null) {
                userCount = 0;
            }
            // 使用新方法
            redisCache.setCacheObjectWithExpire(userKey, userCount + 1, 1, TimeUnit.HOURS);
        }
    }

    /**
     * 清除失败计数
     */
    private void clearFailedAttempts(String ipAddress, String userId) {
        // 清除IP失败计数
        String ipKey = FAILED_ATTEMPTS_IP_PREFIX + ipAddress;
        redisCache.deleteObject(ipKey);

        // 清除用户失败计数
        if (userId != null) {
            String userKey = FAILED_ATTEMPTS_USER_PREFIX + userId;
            redisCache.deleteObject(userKey);
        }
    }

    /**
     * 检查并在需要时锁定
     */
    private boolean checkAndLockIfNeeded(String key, boolean isIp) {
        String countKey = isIp ? FAILED_ATTEMPTS_IP_PREFIX + key : FAILED_ATTEMPTS_USER_PREFIX + key;
        String lockKey = isIp ? LOCKED_IP_PREFIX + key : LOCKED_USER_PREFIX + key;

        Integer count = redisCache.getCacheObject(countKey);
        if (count != null && count >= MAX_FAILED_ATTEMPTS) {
            // 锁定 - 使用新方法
            redisCache.setCacheObjectWithExpire(lockKey, true, LOCK_DURATION_MINUTES, TimeUnit.MINUTES);
            // 清除计数
            redisCache.deleteObject(countKey);
            return true;
        }
        return false;
    }
}